package view.simulationview;

import java.awt.Color;
import java.awt.BasicStroke;
import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.geom.Point2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Observable;
import java.util.Observer;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.SwingConstants;
import controller.commoncontroller.UtilityDialogs;
import controller.commoncontroller.ZoomWheelListener;
import controller.simulationcontroller.ExportTruthTableAsCSVListener;
import view.UtilityLogicDesigns;
import view.UtilityConstants;
import view.commonview.ConnectorPath;
import view.commonview.InfoArea;
import view.commonview.LogicViewElement;
import view.commonview.TabbedPane;
import locsi.UserSettings;
import model.LogicType;
import model.editormodel.Input;
import model.editormodel.Output;
import model.simulationmodel.LogicValue;
import model.simulationmodel.SimulationMessage;
import model.simulationmodel.SimulationModel;
import model.simulationmodel.logicelements.LogicCLOCK;
import model.simulationmodel.logicelements.LogicSimulationElement;

/**
 * <b>Implementiert:</b> <code>Observer</code> <br>
 * <b>Erbt:</b> <code>JPanel</code> <br>
 * <b>In Verbindung stehende Klassen:</b>
 * <code>SimulationModel, LogicViewElement, ConnectorPath, InfoArea</code><br>
 * <b>Ueberwachte Klasse:</b> <code>SimulationModel</code> <br>
 * <b>Funktion:</b> Repraesentation Simulationsbereich <br>
 * <br>
 * Panel, auf dem die Schaltung visualisiert wird (Simulationsbereich). Diese
 * Klasse ueberwacht das <code>SimulationModel</code> und visualisiert die
 * darauf gespeicherten <code>LogicSimulationElement</code>-Objekte und deren
 * Belgungen.
 * 
 * @author Team Locsi
 * 
 */
@SuppressWarnings("serial")
public class SimulationArea extends JPanel implements Observer {

	/*
	 * Referenzen auf andere View-Objekte
	 */
	private SimulationModel simModel;
	private TabbedPane tabPane;
	private InfoArea iArea;
	private SimulationToolPanel stPanel;
	private JScrollPane sScroller;

	/*
	 * Maps und Listen zum Verwalten der Bauteile und Leitungen
	 */
	private Map<String, LogicViewElement> vElements;
	private Map<String, LogicSimulationElement> simElements;
	private Map<String, JLabel> clockLabels;
	private List<LogicViewElement> nodes;
	private List<ConnectorPath> paths;

	/*
	 * Verschiedene Linienarten fuer die paintComponent-Methode
	 */
	private BasicStroke s1;
	private BasicStroke s2;

	/*
	 * Font fuer die Label von Taktgebern
	 */
	private Font labelFont;

	/*
	 * Groesse der SimulationArea zu Beginn
	 */
	private int maxXPos = 800;
	private int maxYPos = 800;
	private Rectangle2D visibleRect;

	/*
	 * Variablen fuer Wahrheitstabelle
	 */
	private ProgressWindow statusWindow;
	private int valueToSet;
	private boolean truthTableGetsCalced;
	private BufferedImage buffImg;

	/**
	 * Dieser Konstruktor erzeugt ein neues <code>SimulationArea</code> -
	 * Objekt. Dabei wird die <code>SimulationArea</code> beim
	 * <code>SimulationModel</code> -Objekt als <code>Observer</code>
	 * angemeldet.
	 * 
	 * @param sModel
	 *            <code>SimulationModel</code> - Referenz auf das
	 *            <code>SimulationModel</code>
	 * @param tabs
	 *            <code>TabbedPane</code> - Referenz auf das
	 *            <code>TabbedPane</code>
	 * @param iArea
	 *            <code>InfoArea</code> - Referenz auf die <code>InfoArea</code>
	 */
	public SimulationArea(SimulationToolPanel stPanel, SimulationModel sModel,
			TabbedPane tabs, InfoArea iArea) {
		// stezen der referenzen und initialisieren der maps und listen
		this.vElements = new HashMap<String, LogicViewElement>();
		this.paths = new ArrayList<ConnectorPath>();
		this.clockLabels = new HashMap<String, JLabel>();
		this.nodes = new ArrayList<LogicViewElement>();
		this.iArea = iArea;
		this.tabPane = tabs;
		this.stPanel = stPanel;
		this.visibleRect = new Rectangle2D.Double();
		this.setBackground(Color.WHITE);
		this.setLayout(null);
		this.setPreferredSize(new Dimension(maxXPos, maxYPos));
		this.labelFont = new Font("Roman", Font.PLAIN, 11);
		// Anlegen der basicstrokes fuer die paincomponent-methode. s1 ist die
		// standard liniendicke, s2 eine dickere linie fuer linien waehrend der
		// simulation
		this.s1 = new BasicStroke(1);
		this.s2 = new BasicStroke(1.3f);

		// adden des zoomwheellisteners
		this.addMouseWheelListener(new ZoomWheelListener(tabPane));

		simModel = sModel;
		simModel.addObserver(this);
	}

	/**
	 * In dieser ueberschriebenen <code>paintComponent</code> Methode werden
	 * alle noetigen Zeichenoperationen fuer die <code>SimulationArea</code>
	 * ausgefuehrt.
	 */
	@Override
	public void paintComponent(Graphics g) {
		super.paintComponent(g);
		Graphics2D g2d = (Graphics2D) g;

		visibleRect.setRect(
				sScroller.getViewport().getViewRect().x
						/ tabPane.getZoomFactor(),
				sScroller.getViewport().getViewRect().y
						/ tabPane.getZoomFactor(),
				sScroller.getViewport().getViewRect().width
						/ tabPane.getZoomFactor(), sScroller.getViewport()
						.getViewRect().height / tabPane.getZoomFactor());

		// wenn die wahrheitstabelle grad berechnet wird, wird das zuvor
		// erstellte
		// bufferbild gezeichnet, weils sonst zu grafikbugs kommt
		if (truthTableGetsCalced) {
			g2d.drawImage(buffImg, null, 0, 0);
		} else {

			// grafikeinstellungen zum weichzeichnen
			if (UserSettings.doAntiAlias) {
				g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
						RenderingHints.VALUE_ANTIALIAS_ON);
			}

			// skalieren, je nachdem was fuer ein zoomfactor eingestellt ist
			g2d.scale(tabPane.getZoomFactor(), tabPane.getZoomFactor());

			// zeichnen der bauteile
			g2d.setStroke(s2);
			paintLogicViewElements(g2d);

			// zeichnen der verbindungen
			paintConnections(g2d);

			// zeichnen der inputs und outputs
			paintInputsAndOutputs(g2d);
		}
	}

	/**
	 * Methode die aehnlich zur paintComponent Methode die Bauteile und
	 * Verbindungen zeichnet. Im Gegensatz zur paintComponent-Methode zeichnet
	 * diese Methode alle Bauteile und Verbindungen, nicht nur die sichtbaren.
	 * Der "Screenshot" wird in ein BufferedImage gezeichnet, welches
	 * zurueckgegeben wird
	 * 
	 * @param img
	 *            BufferedImage, in welches die Bauteile und Verbindungen
	 *            gezeichnet werden sollen
	 * @return img mit den gezeichneten Bauteilen und Verbindungen
	 */
	public BufferedImage paintToImage(BufferedImage img) {
		Graphics2D g2d = img.createGraphics();
		g2d.setColor(Color.white);
		g2d.fillRect(0, 0, img.getWidth(), img.getHeight());
		g2d.setColor(Color.black);
		// grafikeinstellungen zum weichzeichnen
		g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
				RenderingHints.VALUE_ANTIALIAS_ON);

		// skalieren, je nachdem was fuer ein zoomfactor eingestellt ist
		g2d.scale(tabPane.getZoomFactor(), tabPane.getZoomFactor());

		// zeichnen der bauteile
		g2d.setStroke(s2);
		paintLogicViewElements(g2d);

		// zeichnen der verbindungen
		paintConnections(g2d);

		// zeichnen der inputs und outputs
		paintInputsAndOutputs(g2d);

		return img;

	}

	/**
	 * Private Hilfsmethode zum Zeichnen der Bauteile
	 * 
	 * @param g2d
	 *            Graphics2D Objekt aus der paintComponent-Methode
	 */
	private void paintLogicViewElements(Graphics2D g2d) {
		// zeichnen der bauteile
		for (LogicViewElement lVE : vElements.values()) {

			// wenn nicht im sichtbaren bereich -> wird nix gezeichnet
			if (!lVE.getDesign().intersects(visibleRect)) {
				continue;
			}

			g2d.setColor(Color.WHITE);
			g2d.fill(lVE.getDesign());
			g2d.setColor(Color.BLACK);

			// zeichnen der bauteile
			// knoten werden zwischengespeichert und erst nach dem zeichnen der
			// linien gezeichnet (damit linien nicht ueber den knoten liegen)
			if (simElements.get(lVE.getElementsName()).getLogicEditorElement()
					.getType() == LogicType.NODE) {
				nodes.add(lVE);
				// soll eine lampe gezeichnet werden
			} else if (simElements.get(lVE.getElementsName())
					.getLogicEditorElement().getType() == LogicType.LAMP) {
				Color lineColor = g2d.getColor();

				if (simElements.get(lVE.getElementsName()).getLogicValue(
						Output.STD_O) == LogicValue.L1) {
					g2d.setColor(UtilityConstants.LAMP_ON_COLOR);
				} else if (simElements.get(lVE.getElementsName())
						.getLogicValue(Output.STD_O) == LogicValue.UNDEFINED) {
					g2d.setColor(UtilityConstants.LAMP_UD_COLOR);
				} else {
					g2d.setColor(UtilityConstants.LAMP_OFF_COLOR);
				}
				g2d.fill(lVE.getDesign());
				g2d.setColor(lineColor);
				g2d.draw(lVE.getDesign());
				g2d.drawString(lVE.getElementsName(),
						(float) lVE.getSourceX() - 6,
						(float) lVE.getSourceY() - 3);
				// holen der entsprechenden shape fuer einen taktgeber
			} else if (simElements.get(lVE.getElementsName())
					.getLogicEditorElement().getType() == LogicType.CLOCK) {
				if (simElements.get(lVE.getElementsName())
						.getLogicValueOfInput(Input.STD_A) == LogicValue.L1) {
					g2d.setColor(UtilityConstants.LOGIC_VALUE_1);
					// es wird das entsprechende design fuer den taktgeber
					// geholt
					g2d.draw(UtilityLogicDesigns.getClockDesign(simElements
							.get(lVE.getElementsName()).getLogicEditorElement()
							.getXCoord(), simElements
							.get(lVE.getElementsName()).getLogicEditorElement()
							.getYCoord(), LogicValue.L1));
				} else if (simElements.get(lVE.getElementsName())
						.getLogicValueOfInput(Input.STD_A) == LogicValue.L0) {
					g2d.setColor(UtilityConstants.LOGIC_VALUE_0);
					// es wird das entsprechende design fuer den taktgeber
					// geholt
					g2d.draw(UtilityLogicDesigns.getClockDesign(simElements
							.get(lVE.getElementsName()).getLogicEditorElement()
							.getXCoord(), simElements
							.get(lVE.getElementsName()).getLogicEditorElement()
							.getYCoord(), LogicValue.L0));
				} else {
					// es wird das standard-design verwendet
					g2d.setColor(Color.BLACK);
					g2d.draw(lVE.getDesign());
				}
				g2d.drawString(lVE.getElementsName(),
						(float) lVE.getSourceX() - 6,
						(float) lVE.getSourceY() - 3);

				// holen der entsprechenden shape fuer einen schalter
			} else if (simElements.get(lVE.getElementsName())
					.getLogicEditorElement().getType() == LogicType.SWITCH) {
				if (simElements.get(lVE.getElementsName())
						.getLogicValueOfInput(Input.STD_A) == LogicValue.L1) {
					g2d.setColor(UtilityConstants.LOGIC_VALUE_1);
					// es wird das entsprechende design fuer den schalter
					// geholt
					g2d.draw(UtilityLogicDesigns.getSimulationSwithDesign(
							simElements.get(lVE.getElementsName())
									.getLogicEditorElement().getXCoord(),
							simElements.get(lVE.getElementsName())
									.getLogicEditorElement().getYCoord(),
							LogicValue.L1));
				} else if (simElements.get(lVE.getElementsName())
						.getLogicValueOfInput(Input.STD_A) == LogicValue.L0) {
					g2d.setColor(UtilityConstants.LOGIC_VALUE_0);
					// es wird das entsprechende design fuer den schalter
					// geholt
					g2d.draw(UtilityLogicDesigns.getSimulationSwithDesign(
							simElements.get(lVE.getElementsName())
									.getLogicEditorElement().getXCoord(),
							simElements.get(lVE.getElementsName())
									.getLogicEditorElement().getYCoord(),
							LogicValue.L0));
				} else {
					// es wird das standard-design verwendet
					g2d.setColor(Color.BLACK);
					g2d.draw(lVE.getDesign());
				}
				g2d.drawString(lVE.getElementsName(),
						(float) lVE.getSourceX() - 6,
						(float) lVE.getSourceY() - 3);

				// bei selbstdefinierten bauteilen neben der shape auch der name
				// des bauteils und die kuerzel der eingaenge und ausgaenge
				// gezeichnet
			} else if (simElements.get(lVE.getElementsName())
					.getLogicEditorElement().getType() == LogicType.SELFDEFINED) {
				g2d.draw(lVE.getDesign());
				g2d.drawString(lVE.getElementsName(),
						(float) lVE.getSourceX() - 6,
						(float) lVE.getSourceY() - 3);

				// durchlaufen der eingaenge des selbstdefinierten bauteils
				for (Input inp : simElements.get(lVE.getElementsName())
						.getLogicEditorElement().getInputs().values()) {

					// es werden nur die ersten 3 buchstaben des
					// inputnamens angezeigt
					g2d.drawString(
							inp.getOuterName().substring(0,
									Math.min(inp.getOuterName().length(), 3)),
							(float) (lVE.getSourceX() + inp.getXRelToElement()) + 10,
							(float) (lVE.getSourceY() + inp.getYRelToElement()) + 8);
				}

				// durchlaufen der ausgaenge des selbstdefinierten bauteils
				for (Output out : simElements.get(lVE.getElementsName())
						.getLogicEditorElement().getOutputs().values()) {
					// es werden nur die ersten 3 buchstaben des outputnamens
					// angezeigt
					g2d.drawString(
							out.getOuterName().substring(0,
									Math.min(out.getOuterName().length(), 3)),
							(float) (lVE.getSourceX() + out.getXRelToElement())
									- Math.min(out.getOuterName().length(), 3)
									* 8,
							(float) (lVE.getSourceY() + out.getYRelToElement()) + 8);
				}

			} else {
				// g2d.setColor(Color.BLACK);
				g2d.draw(lVE.getDesign());
			}
		}
	}

	/**
	 * Private Hilfsmethode zum Zeichnen der Leitungen
	 * 
	 * @param g2d
	 *            Graphics2D Objekt aus der paintComponent-Methode
	 */
	private void paintConnections(Graphics2D g2d) {
		// zeichnen der verbindungen
		for (int i = 0; i < paths.size(); i++) {

			// wenn nicht im sichtbaren bereich -> wird nix gezeichnet
			if (!paths.get(i).getPath().intersects(visibleRect)) {
				continue;
			}

			LogicValue value = this.simModel.getSimulationElements()
					.get(paths.get(i).getSource().getMyElement())
					.getLogicValue(paths.get(i).getSource().getName());

			// setzen der farbe und des strokes je nach logischem wert
			if (value == LogicValue.UNDEFINED) {
				g2d.setStroke(s1);
				g2d.setColor(Color.GRAY);
			} else if (value == LogicValue.L0) {
				g2d.setStroke(s2);
				g2d.setColor(UtilityConstants.LOGIC_VALUE_0);
			} else if (value == LogicValue.L1) {
				g2d.setStroke(s2);
				g2d.setColor(UtilityConstants.LOGIC_VALUE_1);
			}

			g2d.draw(paths.get(i).getPath());
		}

		g2d.setColor(Color.BLACK);

		// knoten müssen noch gezeichnet werden
		for (LogicViewElement lVE : nodes) {

			// wenn nicht im sichtbaren bereich -> wird nix gezeichnet
			if (!lVE.getDesign().intersects(visibleRect)) {
				continue;
			}

			g2d.fill(lVE.getDesign());
		}
		nodes.clear();
	}

	/**
	 * Private Hilfsmethode zum Zeichnen der Inputs und Outputs
	 * 
	 * @param g2d
	 *            Graphics2D Objekt aus der paintComponent-Methode
	 */
	private void paintInputsAndOutputs(Graphics2D g2d) {
		for (LogicViewElement lVE : vElements.values()) {

			// wenn nicht im sichtbaren bereich -> wird nix gezeichnet
			if (!lVE.getDesign().intersects(visibleRect)) {
				continue;
			}

			// speichern der absoluten koordinaten des bauteils
			// fuer das setzen der inputs nachher noetig
			double absX = lVE.getSourceX();
			double absY = lVE.getSourceY();

			// zeichnen der inputs und outputs, au�er beim bommel
			if (simElements.get(lVE.getElementsName()).getLogicEditorElement()
					.getType() != LogicType.NODE) {

				if (simElements.get(lVE.getElementsName())
						.getLogicEditorElement().getType() != LogicType.SWITCH
						&& simElements.get(lVE.getElementsName())
								.getLogicEditorElement().getType() != LogicType.CLOCK) {
					// zugriff auf die zugehoerigen inputs des logicviewelements
					Map<String, Input> inputs = this.simElements
							.get(lVE.getElementsName()).getLogicEditorElement()
							.getInputs();

					// es wird nun durch die inputs iteriert und diese graphisch
					// als kreis dargestellt
					for (Map.Entry<String, Input> m : inputs.entrySet()) {
						double relX = m.getValue().getXRelToElement();
						double relY = m.getValue().getYRelToElement();
						g2d.setColor(UserSettings.inputColor);
						g2d.fillOval((int) (absX + relX), (int) (absY + relY),
								UtilityConstants.IO_RADIUS,
								UtilityConstants.IO_RADIUS);
						g2d.setColor(Color.BLACK);
						g2d.drawOval((int) (absX + relX), (int) (absY + relY),
								UtilityConstants.IO_RADIUS,
								UtilityConstants.IO_RADIUS);
					}
				}

				if (simElements.get(lVE.getElementsName())
						.getLogicEditorElement().getType() != LogicType.LAMP) {
					// zugriff auf die zugehoerigen outputs des
					// logicviewelements
					Map<String, Output> outputs = this.simElements
							.get(lVE.getElementsName()).getLogicEditorElement()
							.getOutputs();

					// es wird nun durch die outputs iteriert und diese
					// graphisch als kreis dargestellt
					for (Map.Entry<String, Output> m : outputs.entrySet()) {
						double relX = m.getValue().getXRelToElement();
						double relY = m.getValue().getYRelToElement();
						g2d.setColor(UserSettings.outputColor);
						g2d.fillOval((int) (absX + relX), (int) (absY + relY),
								UtilityConstants.IO_RADIUS,
								UtilityConstants.IO_RADIUS);
						g2d.setColor(Color.BLACK);
						g2d.drawOval((int) (absX + relX), (int) (absY + relY),
								UtilityConstants.IO_RADIUS,
								UtilityConstants.IO_RADIUS);
					}
				}
			}
		}
	}

	/**
	 * Hilfsmethode, die beim Aufruf des Erstellen einer Wahrheitstabelle
	 * aufgerufen wird
	 */
	private void startTruthTableCalc() {
		buffImg = new BufferedImage(this.getWidth(), this.getHeight(),
				BufferedImage.TYPE_INT_RGB);
		buffImg = paintToImage(buffImg);
		truthTableGetsCalced = true;
	}

	/**
	 * Gibt die InfoArea zurueck
	 * 
	 * @return InfoArea
	 */
	public InfoArea getiArea() {
		return iArea;
	}

	/**
	 * Abhaengig vom uebergebenen Objekt werden verschiedene Operationen auf den
	 * Attributen der <code>SimulationArea</code> ausgefuehrt, um diese an das
	 * <code>SimulationModel</code> anzupassen.
	 */
	@Override
	public void update(Observable observable, Object data) {
		SimulationMessage msg = (SimulationMessage) data;
		double xCoord = 0;
		double yCoord = 0;
		boolean dimensionChanged = false;
		Point2D loc;

		switch (msg.getMsgType()) {

		// dieser fall wird aufgerufen, wenn man vom bearbeitungsmodus in den
		// simulationsmodus wechselt
		// es werden die gui-objekte und verbindungen erstellt
		case UPDATED_ELEMENTS:
			this.removeAll();
			clockLabels.clear();
			vElements.clear();
			paths.clear();

			simElements = msg.getObjectMap();

			// iterierung ueber die einzelnen bauteile in der vom model in der
			// notifyObserver Methode mituebergebenen simulation-elements
			for (LogicSimulationElement lSE : simElements.values()) {
				xCoord = lSE.getLogicEditorElement().getXCoord();
				yCoord = lSE.getLogicEditorElement().getYCoord();
				loc = new Point2D.Double(xCoord, yCoord);

				// ueberpruefen, ob die aktuelle groesse des simulationsmodus
				// ausreicht
				if (xCoord > maxXPos) {
					maxXPos = (int) xCoord;
					dimensionChanged = true;
				}

				// ueberpruefen, ob die aktuelle groesse des simulationsmodus
				// ausreicht
				if (yCoord > maxYPos) {
					maxYPos = (int) yCoord;
					dimensionChanged = true;
				}

				// falls die groesse nicht reicht, wird die groesse angepasst
				if (dimensionChanged) {
					this.setPreferredSize(new Dimension(maxXPos, maxYPos));
					this.revalidate();
				}

				// erstellen der Linien, indem man alle inputs, die einen
				// zugehoerigen sourceoutput haben mit diesen verbindet
				for (Input inp : lSE.getLogicEditorElement().getInputs()
						.values()) {
					if (inp.getSourceOutput() != null) {
						paths.add(new ConnectorPath(inp.getSourceOutput(), inp));
						paths.get(paths.size() - 1).updatePath();
					}
				}

				// falls man ein selbstdefiniertes bauteil findet, wird
				// entsprechend die noetige Shape zum zeichnen erstellt und im
				// logicviewelement hinterlegt
				if (lSE.getLogicEditorElement().getType() == LogicType.SELFDEFINED) {
					vElements.put(
							lSE.getLogicEditorElement().getName(),
							new LogicViewElement(loc, UtilityLogicDesigns
									.getLogicSelfdefinedDesign(
											loc.getX(),
											loc.getY(),
											Math.max(lSE
													.getLogicEditorElement()
													.getInputs().size(), lSE
													.getLogicEditorElement()
													.getOutputs().size())), lSE
									.getLogicEditorElement().getName()));
				} else {
					vElements
							.put(lSE.getLogicEditorElement().getName(),
									new LogicViewElement(
											loc,
											UtilityLogicDesigns.getLogicDesign(
													lSE.getLogicEditorElement()
															.getType(), loc
															.getX(), loc.getY()),
											lSE.getLogicEditorElement()
													.getName()));

					if (lSE.getLogicEditorElement().getType() == LogicType.CLOCK) {
						JLabel cLabel = createClockLabel(lSE);
						clockLabels.put(lSE.getLogicEditorElement().getName(),
								cLabel);

					}
				}
			}

			this.repaint();
			break;

		// wird nach der durchführung eines simulationsschrittes aufgerufen
		// simulationarea wird dann neu gezeichnet, um die neuen logischen werte
		// zu visualisieren
		case UPDATE_AFTER_CALC:
			this.repaint();
			break;

		// aufruf, wenn die simulation gestoppt wurde
		case STOP_SIMULATION:
			this.stPanel.setSimulationRunningGif(false);
			// entsprechende meldung fuer die infoarea
			this.iArea.addInfoText("Simulation wurde beendet");
			this.repaint();
			break;

		// aufruf wenn die simulation gestartet wurde
		case START_SIMULATION:
			this.stPanel.setSimulationRunningGif(true);
			// entsprechende meldung fuer die infoarea
			this.iArea.addInfoText("Simulation wurde gestartet");
			break;

		// aufruf wenn die simulation pausiert wurde
		case PAUSE_SIMULATION:
			this.stPanel.setSimulationRunningGif(false);
			// entsprechende meldung fuer die infoarea
			this.iArea.addInfoText("Simulation wurde angehalten");
			break;

		// aufruf wenn die simulation wieder aufgenommen wurde
		case RESUME_SIMULATION:
			this.stPanel.setSimulationRunningGif(true);
			// entsprechende meldung fuer die infoarea
			this.iArea.addInfoText("Simulation wurde fortgesetzt");
			break;

		// aufruf, wenn der logische wert an einem taktgeber geaendert wurde
		case SET_CLOCK:
			this.repaint();
			break;
		// aufruf, wenn der logische wert an einem schalter geaendert wurde
		case SET_SWITCH:
			this.repaint();
			break;

		// aufruf, wenn die frequenz eines taktgebers geaendert wurde. diese
		// wird im zugehoerigen label aktualisiert
		case CLOCK_FREQUENCY:
			LogicSimulationElement lSE = msg.getSimElement();
			this.remove(clockLabels.get(lSE.getLogicEditorElement().getName()));
			clockLabels.put(lSE.getLogicEditorElement().getName(),
					createClockLabel(lSE));
			this.repaint();
			this.revalidate();
			break;

		// aufruf, wenn die option wahrheitstabelle erstellen bei mehr als 20
		// schaltern bzw. taktgebern aufgerufen wurde
		case TOO_MUCH_SETTERS:
			// entsprechende meldung wird in der infoarea ausgegeben
			this.iArea
					.addInfoText("Die Option 'Wahrheitstabelle erstellen' ist"
							+ " nur möglich, falls höchstens 20 Schalter/"
							+ "Taktgeber verbaut sind.");
			break;

		// aufruf wenn eine wahrheitstabelle erstellt werden soll (start)
		case START_TRUTH_TABLE:
			startTruthTableCalc();
			this.iArea.addInfoText("Wahrheitstabelle wird berechnet...");
			valueToSet = 0;
			statusWindow = new ProgressWindow(simModel,
					UtilityDialogs.getFrame(), 0, msg.getTruthTable().length);
			UtilityDialogs.getFrame().setEnabled(false);
			statusWindow.setModal(true);
			break;

		// benachrichtigung wenn ein bestimmter prozentsatz der wahrheitstabelle
		// berechnet wurde
		case STATUS_TRUTH_TABLE:
			valueToSet += 10000;
			if (statusWindow != null) {
				statusWindow.setValue(valueToSet);
			}
			break;

		// aufruf wenn die berechnung der wahrheitstabelle fertiggestellt wurde
		case END_TRUTH_TABLE:
			truthTableGetsCalced = false;
			buffImg = null;
			if (statusWindow != null) {
				statusWindow.dispose();
			}
			statusWindow = null;
			UtilityDialogs.getFrame().setEnabled(true);
			// aufrufen der hilfsmethode createtruthtable, die einen jdialog mit
			// der wahrheitstabelle erstellt
			if (msg.getTruthTable() != null) {
				createTruthTable(msg.getTruthTable(), msg.getSetter(),
						msg.getGetter());
			}
			this.revalidate();
			this.repaint();
			break;

		// aufruf wenn eine oszillation gefunden wurde
		case OSZILLATION_FOUND:
			// ist eine oszillation aufgetreten, wird eine entsprechende
			// fehlermeldung an die view weitergeleitet
			lSE = msg.getSimElement();
			this.iArea
					.addInfoText("Bei der Simulation ist eine Oszillation beim Bauteil "
							+ lSE.getLogicEditorElement().getName()
							+ " aufgetreten.");
			break;

		default:
			break;
		}
	}

	/**
	 * Private Hilfsmethode, die ein JLabel fuer einen Taktgeber mit seiner
	 * Frequenz erzeugt
	 * 
	 * @param lSE
	 *            <code>LogicClock</code> - entsprechender Taktgeber
	 * @return JLabel fuer den uebergebenen Taktgeber
	 */
	private JLabel createClockLabel(LogicSimulationElement lSE) {
		JLabel cLabel = new JLabel(((LogicCLOCK) lSE).getFrequency() + " Hz");
		cLabel.setOpaque(true);

		cLabel.setFont(labelFont);
		cLabel.setBackground(Color.WHITE);
		cLabel.setVisible(true);
		cLabel.setLocation((int) lSE.getLogicEditorElement().getXCoord() - 5,
				(int) lSE.getLogicEditorElement().getYCoord() + 28);
		cLabel.setSize(new Dimension(40, 20));
		cLabel.setHorizontalAlignment(SwingConstants.CENTER);
		cLabel.setOpaque(false);
		this.add(cLabel);
		return cLabel;
	}

	/**
	 * Gibt das <code>LogicViewElement</code> an dem Punkt wieder
	 * 
	 * @param point
	 *            <code>Point</code> - ausgewaehlter Bildpunkt in Pixel
	 * 
	 * @return <code>LogicViewElement</code> - das Bauteil das diesen Punkt
	 *         beinhaltet - null wenn kein Element dort liegt
	 */
	public LogicViewElement containsElement(Point2D point) {
		// durchlaeft die map logicviewelements und ueberprueft, ob der
		// uebergebene punkt auf einem dieser bauteile liegt
		for (Map.Entry<String, LogicViewElement> lSE : vElements.entrySet()) {
			if (lSE.getValue().getDesign().contains(point)) {
				return lSE.getValue();
			}
		}

		return null;
	}

	/**
	 * Private Hilfsmethode, die einen <code>JDialog</code> mit einer
	 * Wahrheitstafel fuer die Schaltung erstellt. Bekommt
	 * 
	 * @param tTable
	 *            Matrix von LogicValues
	 * @param setter
	 *            Liste mit den Schaltern und Taktgeber
	 * @param getter
	 *            und Liste mit den Lampen uebergeben
	 */
	private void createTruthTable(final LogicValue[][] tTable,
			List<LogicSimulationElement> setter,
			List<LogicSimulationElement> getter) {

		// string-array fuer die erste zeile der wahrheitstabelle (namen)
		final String[] names = new String[setter.size() + getter.size()];

		JDialog tableDialog = new JDialog(UtilityDialogs.getFrame());
		JPanel namePanel = new JPanel();
		JPanel toolPanel = new JPanel();
		namePanel.add(new JLabel("Wahrheitstabelle für die Schaltung "
				+ this.simModel.getCircuitName()));

		// fuellen des string-arrays names mit den namen der schalter und
		// taktgeber
		for (int i = 0; i < setter.size(); i++) {
			names[i] = setter.get(i).getLogicEditorElement().getName();
		}

		// fuellen des string-arrays names mit den namen der lampen
		for (int i = 0; i < getter.size(); i++) {
			names[i + setter.size()] = getter.get(i).getLogicEditorElement()
					.getName();
		}

		// anlegen einer neuen tabelle
		JTable table = new JTable(tTable, names) {

			@Override
			public boolean isCellEditable(int x, int y) {
				return false;
			}

		};
		table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);

		// skalieren und formatieren des diaglos
		JLabel renderer = ((JLabel) table.getDefaultRenderer(Object.class));
		renderer.setHorizontalAlignment(SwingConstants.CENTER);

		// CSV Export Button
		JButton exportCSVbutton = new JButton(new ImageIcon(getClass().getClassLoader().getResource(
				UtilityConstants.ICON_PATH + "csv.png")));
		exportCSVbutton.addActionListener(new ExportTruthTableAsCSVListener(
				this, simModel, tTable, names));
		exportCSVbutton.setToolTipText("Tabelle in CSV-Format exportieren");
		toolPanel.add(exportCSVbutton);

		tableDialog.setLayout(new BorderLayout());
		tableDialog.add(namePanel, BorderLayout.NORTH);
		tableDialog.add(new JScrollPane(table), BorderLayout.CENTER);
		tableDialog.add(toolPanel, BorderLayout.SOUTH);
		tableDialog.pack();
		tableDialog.setVisible(true);
	}

	/**
	 * Diese Methode setzt die Variable sScroller
	 * 
	 * @param simScroller
	 *            <code>JScrollPane</code> - Scrolleiste
	 */
	public void setSimScroller(JScrollPane simScroller) {
		this.sScroller = simScroller;
	}
}